package mx.com.crowdgine.display.animation.mocap;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class ASFFile {
	
	// value tags
	private static final String VERSION_TAG = ":version";
	private static final String NAME_TAG = ":name";
	private static final String BONE_NAME_TAG = "name";
	private static final String UNITS_TAG = ":units";
	private static final String MASS_TAG = "mass";
	private static final String LENGTH_TAG = "length";
	private static final String ANGLE_TAG = "angle";
	private static final String DOCUMENTATION_TAG = ":documentation";
	private static final String ROOT_TAG = ":root";
	private static final String ORDER_TAG = "order";
	private static final String AXIS_TAG = "axis";
	private static final String POSITION_TAG = "position";
	private static final String ORIENTATION_TAG = "orientation";
	private static final String BONEDATA_TAG = ":bonedata";
	private static final String BEGIN_TAG = "begin";
	private static final String ID_TAG = "id";
	private static final String DIRECTION_TAG = "direction";
	private static final String DOF_TAG = "dof";
	private static final String LIMITS_TAG = "limits";
	private static final String END_TAG = "end";
	private static final String HIERARCHY_TAG = ":hierarchy";
	
	// file sections
	private static final int UNITS = 1;
	private static final int DOCUMENTATION = 2;
	private static final int ROOT = 3;
	private static final int BONEDATA = 4;
	private static final int HIERARCHY = 5;
	
	// value holders
	private String fileName;
	private String version;
	private String name;
	private float unitsMass;
	private float unitsLength;
	private String unitsAngle;
	private String documentation = "";
	private String [] rootOrders;
	private String rootAxis;
	private float [] rootPosition;
	private float [] rootOrientation;
	private List<ASFBone> bones =  new ArrayList<ASFBone>();
	
	public ASFFile(String fileName) {
		this.fileName = fileName;
		parse();
	}
	
	private void parse() {
		try {
			BufferedReader in = new BufferedReader(new FileReader(this.fileName));
			
			String line = in.readLine();
			
			int currentSection = -1;
			ASFBone currentBone = null;
			
			while(line != null) {
				
				if(!line.startsWith("#")) {
					// ignore comments
					
					if(line.startsWith(VERSION_TAG)) {
						this.version = getTagValue(line, VERSION_TAG);
					} else if(line.startsWith(NAME_TAG)) {
						this.name = getTagValue(line, NAME_TAG);
					} else if(line.startsWith(UNITS_TAG)) {
						currentSection = UNITS;
					} else if(line.startsWith(DOCUMENTATION_TAG)) {
						currentSection = DOCUMENTATION;
					} else if(line.startsWith(ROOT_TAG)) {
						currentSection = ROOT;
					} else if(line.startsWith(BONEDATA_TAG)) {
						currentSection = BONEDATA;
					} else if(line.startsWith(HIERARCHY_TAG)){
						currentSection = HIERARCHY;
					} else {
					
					
						switch(currentSection) {
							case UNITS:
								if(line.trim().startsWith(MASS_TAG)) {
									this.unitsMass = Float.parseFloat(getTagValue(line.trim(), MASS_TAG));
								} else if(line.trim().startsWith(LENGTH_TAG)) {
									this.unitsLength = Float.parseFloat(getTagValue(line.trim(), LENGTH_TAG));
								} else if(line.trim().startsWith(ANGLE_TAG)) {
									this.unitsAngle = getTagValue(line.trim(), ANGLE_TAG);
								}
								break;
								
							case DOCUMENTATION:
								this.documentation += line + "\n";
								break;
								
							case ROOT:
								
								if(line.trim().startsWith(ORDER_TAG)) {
									this.rootOrders = getTagValue(line.trim(), ORDER_TAG).split("\\s+");
									
								} else if(line.trim().startsWith(AXIS_TAG)){
									this.rootAxis = getTagValue(line.trim(), AXIS_TAG);
								} else if(line.trim().startsWith(POSITION_TAG)) {
									
									String [] positions = getTagValue(line.trim(), POSITION_TAG).split("\\s+");
									
									this.rootPosition = new float[positions.length];
									
									int i = 0;
									for(String position : positions) {
										this.rootPosition[i] = Float.parseFloat(position);
										i++;
									}
									
								} else if(line.trim().startsWith(ORIENTATION_TAG)) {
									String [] orientations = getTagValue(line.trim(), ORIENTATION_TAG).split("\\s+");
									
									this.rootOrientation = new float[orientations.length];
									
									int i = 0;
									for(String orientation : orientations) {
										this.rootOrientation[i] = Float.parseFloat(orientation);
										i++;
									}
								}
								
								break;
								
							case BONEDATA:
								
								if(line.trim().startsWith(BEGIN_TAG)) {
									currentBone = new ASFBone();
									this.bones.add(currentBone);
								} else if(line.trim().startsWith(ID_TAG)) {
									currentBone.setId(Integer.parseInt(getTagValue(line.trim(), ID_TAG)));
								} else if(line.trim().startsWith(BONE_NAME_TAG)) {
									currentBone.setName(getTagValue(line.trim(), BONE_NAME_TAG));
								} else if(line.trim().startsWith(DIRECTION_TAG)) {
									String [] directions = getTagValue(line.trim(), DIRECTION_TAG).split("\\s+");
									currentBone.setDirection(new float[directions.length]);
									int i = 0;
									for(String direction : directions) {
										currentBone.getDirection()[i] = Float.parseFloat(direction);
										i++;
									}
								} else if(line.trim().startsWith(LENGTH_TAG)){
									currentBone.setLength(Float.parseFloat(getTagValue(line.trim(), LENGTH_TAG)));
								} else if(line.trim().startsWith(AXIS_TAG)) {
									String [] axisValues = getTagValue(line.trim(), AXIS_TAG).split("\\s+");
									currentBone.setAxisOrientation(new float[3]);
									currentBone.getAxisOrientation()[0] = Float.parseFloat(axisValues[0]);
									currentBone.getAxisOrientation()[1] = Float.parseFloat(axisValues[1]);
									currentBone.getAxisOrientation()[2] = Float.parseFloat(axisValues[2]);
									currentBone.setAxisInitialRotationOrder(axisValues[3]);
								} else if(line.trim().startsWith(DOF_TAG)) {
									currentBone.setDof(getTagValue(line.trim(), DOF_TAG).split("\\s+"));
								} else if(line.trim().startsWith(LIMITS_TAG)) {
									
									String [] limitBounds = getTagValue(line.trim(), LIMITS_TAG).split("\\s+");
									ASFBoneLimit limit = getASFBoneLimit(limitBounds);
									currentBone.getLimits().add(limit);
									
								} else if(!line.trim().startsWith(END_TAG)) {
									String [] limitBounds = line.trim().split("\\s+");
									ASFBoneLimit limit = getASFBoneLimit(limitBounds);
									currentBone.getLimits().add(limit);
								}
									
								
								break;
							case HIERARCHY:
								
								if(!line.trim().startsWith(BEGIN_TAG) && !line.trim().startsWith(END_TAG)) {
									String [] boneNames = line.trim().split("\\s+");
									
									setupBoneHierarchy(boneNames);
									
								}
								
								break;
						}
					}
					
				}
				
				line = in.readLine();
			}
			
			
		} catch(Exception ex) {
			System.err.println("Error parsing ASF file");
			ex.printStackTrace();
			throw new RuntimeException(ex);
		}
	}

	private void setupBoneHierarchy(String[] boneNames) {
		try {
			ASFBone parentBone = getBone(boneNames[0]);
			ASFBone childBone = null;
			for(int i = 1; i < boneNames.length; i++) {
				childBone = getBone(boneNames[i]);
				childBone.setParentId(parentBone.getId());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
	public ASFBone getBone(String boneName) {
		ASFBone matchingBone = null;
		
		if(boneName.equals("root")) {
			matchingBone = new ASFBone();
		} else {

			for(ASFBone bone : bones) {
				if(bone.getName().equals(boneName)) {
					matchingBone = bone;
					break;
				}
			}
		}
		
		return matchingBone;
	}
	

	private ASFBoneLimit getASFBoneLimit(String[] limitBounds) {
		ASFBoneLimit limit = null;
		try {
			String lowerBound = limitBounds[0].substring(1);
			String upperBound = limitBounds[1].substring(0, limitBounds[1].length() - 1);
			
			limit = new ASFBoneLimit(Float.parseFloat(lowerBound), Float.parseFloat(upperBound));
			return limit;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return limit;
	}

	private String getTagValue(String line, String tag) {
		return line.substring(line.indexOf(tag) + tag.length()+1);
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getVersion() {
		return version;
	}

	public void setVersion(String version) {
		this.version = version;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public float getUnitsMass() {
		return unitsMass;
	}

	public void setUnitsMass(float unitsMass) {
		this.unitsMass = unitsMass;
	}

	public float getUnitsLength() {
		return unitsLength;
	}

	public void setUnitsLength(float unitsLength) {
		this.unitsLength = unitsLength;
	}

	public String getUnitsAngle() {
		return unitsAngle;
	}

	public void setUnitsAngle(String unitsAngle) {
		this.unitsAngle = unitsAngle;
	}

	public String getDocumentation() {
		return documentation;
	}

	public void setDocumentation(String documentation) {
		this.documentation = documentation;
	}

	public String[] getRootOrders() {
		return rootOrders;
	}

	public void setRootOrders(String[] rootOrders) {
		this.rootOrders = rootOrders;
	}

	public String getRootAxis() {
		return rootAxis;
	}

	public void setRootAxis(String rootAxis) {
		this.rootAxis = rootAxis;
	}

	public float[] getRootPosition() {
		return rootPosition;
	}

	public void setRootPosition(float[] rootPosition) {
		this.rootPosition = rootPosition;
	}

	public float[] getRootOrientation() {
		return rootOrientation;
	}

	public void setRootOrientation(float[] rootOrientation) {
		this.rootOrientation = rootOrientation;
	}

	public List<ASFBone> getBones() {
		return bones;
	}

	public void setBones(List<ASFBone> bones) {
		this.bones = bones;
	}
	
	
}
